#include "imageADT.h"

/**
*   \var _BM_
*   Valor del unsigned short que representa BM.
*/
#define _BM_ 19778

/**
*   \struct BITMAPFILEHEADER
*   Primer header que contiene informacion acerca de la imagen.
*   @param type Valor que tiene que ser igual a _BM_.
*   @param size tamanio de la imagen.
*   @param reserved1 Espacio reservado.
*   @param reserved2 Espacio reservado.
*   @param offsetbits Offset donde inicia la imagen.
*/ 
typedef struct{
    unsigned short type;
    unsigned long size;
    unsigned short reserved1;
    unsigned short reserved2;
    unsigned long offsetbits;
}BITMAPFILEHEADER;

/**
*   \struct BITMAPINFOHEADER
*   Segundo header que contiene informacion acerca de la imagen.
*   @param size Tamanio del header.
*   @param width Ancho de la imagen.
*   @param height Altura de la imagen.
*   @param planes Planos de la imagen.
*   @param bitcount Profundidad de la imagen.
*   @param compression Compresion de la imagen.
*   @param sizeimage Tamanio de la imagen.
*   @param xpelsppermeter Dato dentro del header del bmp que no se usa en el programa.
*   @param ypelsppermeter Dato dentro del header del bmp que no se usa en el programa.
*   @param colorused Dato dentro del header del bmp que no se usa en el programa.
*   @param colorsimportant Dato dentro del header del bmp que no se usa en el programa. 
*/ 
typedef struct{
    unsigned long size;
    unsigned long width;
    unsigned long height;
    unsigned short planes;
    unsigned short bitcount;
    unsigned long compression;
    unsigned long sizeimage;
    long xpelspermeter;
    long ypelspermeter;
    unsigned long colorsused;
    unsigned long colorsimportant;
}BITMAPINFOHEADER;

/**
*   \struct SINGLE_PIXEL
*   Estructura correspondiente a un pixel. Contiene el RGB.
*   @param blue Valor del azul dentro del RGB.
*   @param green Valor del verde dentro del RGB.
*   @param red Valor del rojo dentro del RGB. 
*/ 
typedef struct{
    unsigned char blue;
    unsigned char green;
    unsigned char red;
}SINGLE_PIXEL;

/**
*   \struct imageCDT
*   Estructura del header de la imagen.
*   @param header Puntero a la estructura de tipo BITMAPFILEHEADER.
*   @param info Puntero a la estructura de tipo BITMAPINFOHEADER.
*   @param qTree Puntero al arbol.
*   @param sizeFixed Entero del tamanio del cuadrado 2^n.
*   @param n Entero por el cual se eleva dos para obtener sizeFixed.
*/ 
struct imageCDT
{
    BITMAPFILEHEADER * header;
    BITMAPINFOHEADER * info;
    FILE *file;
};

static int readFirstHeader(imageADT image, FILE * source);
static int writeFirstHeader(imageADT image, FILE * dest);
static int readSecondHeader(imageADT image, FILE * source);
static int writeSecondHeader(imageADT image, FILE * dest);
static int writeImage(imageADT image, FILE * dest);

imageADT openImage(char *file)
{
    imageADT image;
    FILE * source;


    if( (image = malloc(sizeof(struct imageCDT))) == NULL )
        return NULL;

    if( (source = fopen( file, "rb")) == NULL )
    {
        free(image);
        return NULL;
    }
    if( !readFirstHeader(image, source) )
    {
        free(image);
        return NULL;
    }

    if( !readSecondHeader(image, source) )
    {
        free(image);
        return NULL;
    }

    image->file = source;

    return image;
}

FILE *getFile(imageADT image) {
    if ( image == NULL ) {
        return NULL;
    }

    return image->file;
}

int isMod(imageADT image, int mod) {
    if ( image == NULL || mod <= 0 ) {
        return 0;
    }

    return (image->info->sizeimage % mod) == 0;
}

int saveImage(imageADT image, char * file)
{
    FILE * dest;

    if( image == NULL || file == NULL)
        return 0;

    if( (dest = fopen( file, "wb+")) == NULL )
        return 0;

    if( !writeFirstHeader(image, dest) )
        return 0;

    if( !writeSecondHeader(image, dest) )
        return 0;

    if( !writeImage(image, dest) )
        return 0;

    fclose(dest);

    return 1;
}

FILE * createOutputFile(imageADT image, char *file)
{
    FILE * dest;

    if( image == NULL || file == NULL)
        return NULL;

    if( (dest = fopen( file, "wb+")) == NULL )
        return NULL;

    if( !writeFirstHeader(image, dest) )
        return NULL;

    if( !writeSecondHeader(image, dest) )
        return NULL;

    return dest;
}

/*
 * Esta funcion recorre el cuadrado del quadTree viendo
 * si los pixeles existen o no.
 * Sin importar la transformacion que se le hizo a la
 * imagen, va a dejarla bien.
 * El flag se usa para que se escriban los bytes para que la 
 * fila sea multi de cuatro siempre y cuando los pixeles que
 *  se hayan leido en esa linea sean != NULL.
*/

static int writeImage(imageADT image, FILE * dest)
{
    char ch;

    while(!feof(image->file)) {
        ch = fgetc(image->file);
        if(ferror(image->file)) {
            return 0;
        }
        if(!feof(image->file))
            fputc(ch, dest);

        if(ferror(dest)) {
            return 0;
        }
    }

    return 1;
}

static int writeFirstHeader(imageADT image, FILE * dest)
{
    if( image == NULL || dest == NULL)
        return 0;

    if( (fwrite(&image->header->type, sizeof(unsigned short), 1, dest)) == 0 )
        return 0;

    if( (fwrite( &image->header->size, sizeof(unsigned long), 1, dest)) == 0 )
        return 0;

    if( (fwrite( &image->header->reserved1, sizeof(unsigned short), 1, dest)) == 0 )
        return 0;

    if( (fwrite( &image->header->reserved2, sizeof(unsigned short), 1, dest)) == 0 )
        return 0;

    if( (fwrite( &image->header->offsetbits, sizeof(unsigned long), 1, dest)) == 0 )
        return 0;

    return 1;
}

static int readFirstHeader(imageADT image, FILE * source)
{

    if( image == NULL || source == NULL)
        return 0;

    if( (image->header = malloc(sizeof(BITMAPFILEHEADER))) == NULL )
        return 0;

    if( (fread( &image->header->type, sizeof(unsigned short), 1, source )) == 0 )
        return 0;

    if( (fread( &image->header->size, sizeof(unsigned long), 1, source )) == 0 )
        return 0;

    if( (fread( &image->header->reserved1, sizeof(unsigned short), 1, source )) == 0 )
        return 0;

    if( (fread( &image->header->reserved2, sizeof(unsigned short), 1, source )) == 0 )
        return 0;

    if( (fread( &image->header->offsetbits, sizeof(unsigned long), 1, source )) == 0 )
        return 0;

    return 1;
}
static int writeSecondHeader(imageADT image, FILE * dest)
{
    if( image == NULL || dest == NULL)
        return 0;

    if( (fwrite( &image->info->size, sizeof(unsigned long), 1, dest )) == 0 )
        return 0;

    if( (fwrite( &image->info->width, sizeof(unsigned long), 1, dest )) == 0 )
        return 0;

    if( (fwrite( &image->info->height, sizeof(unsigned long), 1, dest )) == 0 )
        return 0;

    if( (fwrite( &image->info->planes, sizeof(unsigned short), 1, dest )) == 0 )
        return 0;

    if( (fwrite( &image->info->bitcount, sizeof(unsigned short), 1, dest )) == 0 )
        return 0;

    if( (fwrite( &image->info->compression, sizeof(unsigned long), 1, dest )) == 0 )
        return 0;

    if( (fwrite( &image->info->sizeimage, sizeof(unsigned long), 1, dest )) == 0 )
        return 0;

    if( (fwrite( &image->info->xpelspermeter, sizeof(long), 1, dest )) == 0 )
        return 0;

    if( (fwrite( &image->info->ypelspermeter, sizeof(long), 1, dest )) == 0 )
        return 0;

    if( (fwrite( &image->info->colorsused, sizeof(unsigned long), 1, dest )) == 0 )
        return 0;

    if( (fwrite( &image->info->colorsimportant, sizeof(unsigned long), 1, dest )) == 0 )
        return 0;

    return 1;
}

static int readSecondHeader(imageADT image, FILE * source)
{
    if( image == NULL || source == NULL)
        return 0;

    if( (image->info = malloc(sizeof(BITMAPINFOHEADER))) == NULL )
        return 0;

    if( (fread( &image->info->size, sizeof(unsigned long), 1, source )) == 0 )
        return 0;

    if( (fread( &image->info->width, sizeof(unsigned long), 1, source )) == 0 )
        return 0;

    if( (fread( &image->info->height, sizeof(unsigned long), 1, source )) == 0 )
        return 0;

    if( (fread( &image->info->planes, sizeof(unsigned short), 1, source )) == 0 )
        return 0;

    if( (fread( &image->info->bitcount, sizeof(unsigned short), 1, source )) == 0 )
        return 0;

    if( (fread( &image->info->compression, sizeof(unsigned long), 1, source )) == 0 )
        return 0;

    if( (fread( &image->info->sizeimage, sizeof(unsigned long), 1, source )) == 0 )
        return 0;

    if( (fread( &image->info->xpelspermeter, sizeof(long), 1, source )) == 0 )
        return 0;

    if( (fread( &image->info->ypelspermeter, sizeof(long), 1, source )) == 0 )
        return 0;

    if( (fread( &image->info->colorsused, sizeof(unsigned long), 1, source )) == 0 )
        return 0;

    if( (fread( &image->info->colorsimportant, sizeof(unsigned long), 1, source )) == 0 )
        return 0;

    return 1;
}

int printHeader(imageADT image)
{
    if( image == NULL )
        return 0;

    if(image->header == NULL )
        return 0;

    printf("Type: %d\n", image->header->type);
    printf("Size: %ld\n", image->header->size);
    printf("Reserved1: %d\n", image->header->reserved1);
    printf("Reserved2: %d\n", image->header->reserved2);
    printf("Offsetbits: %ld\n", image->header->offsetbits);

    return 1;
}

int printInfo(imageADT image)
{
    if( image == NULL )
        return 0;

    if(image->header == NULL )
        return 0;

    printf("Size: %ld\n", image->info->size);
    printf("Width: %ld\n", image->info->width);
    printf("Height: %ld\n", image->info->height);
    printf("Planes: %d\n", image->info->planes);
    printf("Bitcount: %d\n", image->info->bitcount);
    printf("Compression: %ld\n", image->info->compression);
    printf("Sizeimage: %ld\n", image->info->sizeimage);
    printf("Xpelspermeter: %ld\n", image->info->xpelspermeter);
    printf("Ypelspermeter: %ld\n", image->info->ypelspermeter);
    printf("Colorused: %ld\n", image->info->colorsused);
    printf("Colorsimportant: %ld\n", image->info->colorsimportant);

    return 1;
}

int closeImage(imageADT image)
{
    if( image == NULL )
        return 0;

    free(image->header);
    free(image->info);
    fclose(image->file);
    free(image);

    return 1;
}

/*
 * headerCheck verifica que la imagen sea valida.
 * Por ejemplo si es una imagen de 24 bits
*/
int headerCheck(imageADT image)
{
    if( image == NULL )
        return 0;

    if( image->info == NULL || image->header == NULL )
        return 0;

    if( image->header->offsetbits != 54 ||
        image->header->size <= 0        ||
        image->header->type != _BM_     ||

        image->info->bitcount != 24     ||
        image->info->compression != 0   ||
        image->info->height <= 0        ||
        image->info->width <= 0         )

        return 0;

    return 1;
}